// 一、判断题  √ √ ×  √  √ √

// 二、简答题

// 1、
// 定义一个接口 Product，该接口包括以下属性: id: 数字类型，该属性为只读属性，表示产品的唯一标识符。name: 字符串类型，表示产品的名称。price: 数字类型，表示产品的价格。description: 字符串类型，表示产品的描述，该属性为可选属性。
// 要求:使用该接口创建一个对象 product1，其中 description 属性为空。

interface Product {
    readonly id: number
    name: string
    price: number
    description?: string
}

let product1: Product = {
    id: 1,
    name: '柠檬水',
    price: 4,
    description: ''
}

// 2、使用第一题 Product 接口，声明一个 Product 对象数组 类型。

let product2: Product[]


// 3、、使用类型别名定义一个函数类型 Greet,该函数接收一个参数 name,类型为 sting,且为可选参数,返回值类型为 sting。然后使用该类型声明一个问候函数 greet。

type Greet = (name?: string) => string

let greet: Greet = (name?: string) => {
    return 'hi~'
}

console.log(greet()); // asd



// 4、将行字面量联合类型 type Status='red"|"green";改写为常量枚举类型。

const enum Status {
    Red = 'red',
    Green = 'green'
}


// 5、以下两个类型组合的结果是什么类型?
// typeUser = { name: string; age: number; } & { email: string; name: string; }

type User = {
    name: string;
    age: number;
    email: string;
}


// 6、使用泛型改写以下类型，在使用泛型声明的时候再决定 play 属性具体的类型，并写出声明示例。
// interface lmeiny {
//     name: string
//     love: string
//     play: string | ClassB | ClassA | InterfaceA
// }

// // 假设这些是可能的类型  
class ClassA {
    type = 'ClassA';
    details: string;
    constructor(details: string) {
        this.details = details;
    }
}

class ClassB {
    type = 'ClassB';
    details: number;
    constructor(details: number) {
        this.details = details;
    }
}

interface InterfaceA {
    type: 'InterfaceA';
    info: string;
}

// 使用泛型来定义接口  
interface Lmeiny<T> {
    name: string;
    love: string;
    play: T; // 使用泛型 T 作为 play 的类型  
}


// 声明示例  
// 假设我们想要 play 属性是 ClassA 类型的实例  
let example1: Lmeiny<ClassA> = {
    name: "Alice",
    love: "Books",
    play: new ClassA("Reading")
};

// 假设我们想要 play 属性是 InterfaceA 类型的对象  
let example2: Lmeiny<InterfaceA> = {
    name: "Bob",
    love: "Sports",
    play: {
        type: 'InterfaceA',
        info: "Basketball"
    }
};

// 假设我们想要 play 属性是简单的字符串  
let example3: Lmeiny<string> = {
    name: "Charlie",
    love: "Music",
    play: "Guitar"
};

// 假设我们想要 play 属性是 ClassB 类型的实例  
let example4: Lmeiny<ClassB> = {
    name: "David",
    love: "Math",
    play: new ClassB(42)
};





// 7、使用泛型改写以下函数加上类型控制参数 key 必须是 obj 中的键名。

// const obj2 = { e: 'eee', f: 'fff', g: 'ggg', h: 'hhhh' }
// function getObjltem(obj, key) {
//     return obj[key]
// }
// getObjltem(obj2, 'e')


const obj2 = { e: 'eee', f: 'fff', g: 'ggg', h: 'hhhh' }
function getObjltem<T extends Record<string, string>, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}
getObjltem(obj2, 'e')

// 8、写出内置扩展类型 Partial，Pick，Omit，Record，Readonly 的用途，以及基本语法使用案例。

// Partial<T>: 将传入类型的所有属性变为可选的，需要处理只包含对象部分属性的场景中
// 基本语法：
// type Partial<T> = {
//     [P in keyof T]?: T[P];
// };
// 使用案例：
interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type PartialTodo = Partial<Todo>;

let todo: PartialTodo = {
    title: "Clean room",
    // description 和 completed 可以不提供  
};

// Pick<T, K>：从传入类型 T 中挑选一组属性 K，构造一个新的类型，仅包含这些属性。用于在需要操作对象的子集时。
// 基本语法：
// type Pick<T, K extends keyof T> = {
//     [P in K]: T[P];
// };
// 使用案例：
interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>;

let todoPreview: TodoPreview = {
    title: "Learn TypeScript",
    completed: false,
    // description 不被允许  
};

// Omit<T, K> ：构造一个省略传入类型 T 中 K 集合属性的类型。用于在需要排除对象某些属性时。
// 基本语法（虽然它是内置的，但概念上可以这样理解）：
// type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// 使用案例
interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoInProgress = Omit<Todo, 'completed'>;

let todoInProgress: TodoInProgress = {
    title: "Write more code",
    description: "Improve the project",
    // completed 不被允许  
};

// Record<K, T> ：构造一个对象类型，其属性键 K 是一个集合，属性值 T 是统一的类型。用于需要创建具有相同值类型的字典或映射时。
// 基本语法：
// type Record<K extends keyof any, T> = {
//     [P in K]: T;
// };
// 使用案例：
type StringRecord = Record<string, number>;

let ageRecord: StringRecord = {
    "Alice": 30,
    "Bob": 25
};


// Readonly<T> ：创建一个类型，其属性都是只读的。用于需要保护对象不被修改时。
// 基本语法（内部逻辑更复杂，但概念上可以这样理解）：
// type Readonly<T> = {
//     readonly [P in keyof T]: T[P];
// };
// 使用案例：
interface MutableTodo {
    title: string;
    description: string;
}

type ReadonlyTodo = Readonly<MutableTodo>;

let todo2: ReadonlyTodo = {
    title: "Set up meeting",
    description: "Discuss next quarter goals"
};
todo.title = "Different meeting"; // 这会报错，因为属性是只读的



// 9、使用 declare module 给原有模块'@vue/runtime-core’扩展一个接口类型 ClobalComponents(属性有 Chooselmg、ElAlert 类型都是 string) 并导出。


declare module '@vue/runtime-core' {
    export interface ClobalComponents {
        Chooselmg: string
        ElAlert: string
    }

}



// 13、描述出以下代码调用部分的运行流程和运行结果:
function fun1() {

    return new Promise((resolve) => {
        setTimeout(() => {
            // console.log('范声强');
            resolve('范声强')
        }, 1000);
    })
}


function fun2() {

    return new Promise((resolve) => {
        setTimeout(() => {
            // console.log('范声强');
            resolve('张长春')
        }, 1000);
    })
}

// fun1函数返回一个 Promise对象 
fun1().then((res) => {

    console.log(res);
    // 返回  调用fun2函数 返回fun2里边的 Promise对象
    return fun2()

}).then((res) => {
    console.log(res);
})

// 1.	调用 fun1，启动一个1秒后解析的 Promise。
// 2.	fun1 的 Promise 解析后，打印 '范声强'。
// 3.	调用 fun2，启动另一个1秒后解析的 Promise。
// 4.	fun2 的 Promise 解析后，打印 '张长春'。


// 14、说出ts 中的类型关键字 keyof、typeof、in、instanceof、is 的作用以及使用场景

// keyof:
// 作用：keyof 类型查询操作符用于获取一个类型的所有键名，这些键名可以是类型中的属性名或者是索引签名的键。
// 使用场景：当你需要操作一个类型的所有键或者想要获取一个对象的键名组成的联合类型时。
// typeof:
// 作用：typeof 用于获取一个变量或对象的类型。
// 使用场景：当你需要在类型层面上引用一个变量的类型时，或者在高级类型中引用一个类型的实例类型时。
// in:
// 作用：in 是一个条件类型，用于在编译时检查一个属性是否存在于某个类型中。
// 使用场景：当你需要基于属性的存在与否来生成不同的类型时。
// instanceof:
// 作用：instanceof 在TypeScript中不作为类型操作符，而是作为运行时的类型检查操作符。它用于检查一个对象是否是特定类的实例。
// 使用场景：在运行时确定一个对象的类型，以便进行类型安全的类型转换或条件分支。
// is:
// 作用：is 不是TypeScript语言规范的一部分，但它是TypeScript社区中常见的模式，用于类型守卫。
// 使用场景：在函数或方法中，当你需要一个类型守卫来确保一个变量是特定类型时。